﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using PdfSdk;
using PdfSdk.PdfViewer;
using Syncfusion.Pdf;
using Syncfusion.Pdf.Parsing;
using Syncfusion.Windows.PdfViewer;

namespace PdfControlApp.Pdf
{
    public class PdfControl : Control, IPdfControl
    {
        private Grid _grid;
        private PdfDocumentView _pdv;
        private PdfDocumentViewWrapper _pdvWrapper;
        private DocumentView _dv;
        private DocumentViewWrapper _dvWrapper;
        private string _documentPath;
        private readonly OriginalFile _originalFile = new();

        public event EventHandler GetDocumentPassword;

        public event EventHandler DocumentLoaded;

        public event EventHandler DocumentUnloaded;

        public string ReferencePath { get; set; }

        public bool IsDocumentEdited { get; set; }

        public PdfLoadedDocument LoadedDocument
        {
            get
            {
                return _dv?.LoadedDocument;
            }
        }

        static PdfControl()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(PdfControl), new FrameworkPropertyMetadata(typeof(PdfControl)));
        }

        public PdfControl()
        {
            Unloaded += PdfControl_Unloaded;
        }

        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();

            _grid = (Grid)GetTemplateChild("PART_Grid");
        }

        private void PdfControl_Unloaded(object sender, RoutedEventArgs e)
        {
            Unload();
        }

        public void Load(string filePath, string password = null)
        {
            if (!File.Exists(filePath))
                throw new FileNotFoundException("Cannot able to locate the specified file");

            if (password == null)
                password = string.Empty;

            if (_pdv != null)
            {
                _pdvWrapper.UnloadEx();
                _grid.Children.Clear();
                _pdv = null;
            }

            _pdv = AddPdfDocumentView();
            _pdvWrapper = _pdv.GetWrapper();

            try
            {
                using FileStream stream = File.OpenRead(filePath);
                Stream newCopiedStream = CreateNewCopiedStream(stream);
                PdfLoadedDocument loadedDocument = new(newCopiedStream, password);

                _pdv.Load(loadedDocument);

                _documentPath = filePath;
                _originalFile.Lock(_documentPath);
            }
            catch (PdfInvalidPasswordException)
            {
                if (GetDocumentPassword != null)
                {
                    GetDocumentPassword(this, EventArgs.Empty);
                }
                else
                {
                    throw;
                }
            }
        }

        public void Reload()
        {
            PdfLoadedDocument loadedDocument = LoadedDocument;
            if (loadedDocument == null)
                return;

            if (_pdv != null)
            {
                _pdvWrapper.UnloadEx();
                _grid.Children.Clear();
                _pdv = null;
            }

            _pdv = AddPdfDocumentView();
            _pdvWrapper = _pdv.GetWrapper();

            _pdv.Load(loadedDocument);
        }

        public void Unload()
        {
            if (_pdv != null)
            {
                _pdvWrapper.UnloadEx();
                _grid.Children.Clear();
                _pdv = null;
            }

            _originalFile.Unlock();
        }

        private PdfDocumentView AddPdfDocumentView()
        {
            PdfDocumentView pdfDocumentView = new PdfDocumentView();

            pdfDocumentView.SetBinding(WidthProperty,
                new Binding(ActualWidthProperty.Name)
                {
                    ElementName = nameof(_grid)
                });
            pdfDocumentView.SetBinding(HeightProperty,
                new Binding(ActualHeightProperty.Name)
                {
                    ElementName = nameof(_grid)
                });

            pdfDocumentView.SetBinding(PdfDocumentView.IsDocumentEditedProperty,
                new Binding(nameof(IsDocumentEdited))
                {
                    Mode = BindingMode.OneWayToSource,
                    Source = this,
                });

            if (!string.IsNullOrEmpty(ReferencePath))
            {
                pdfDocumentView.ReferencePath = ReferencePath;
            }

            pdfDocumentView.PageBorder.IsVisible = false;
            pdfDocumentView.DocumentLoaded += _pdfDocumentView_DocumentLoaded;
            pdfDocumentView.DocumentUnloaded += _pdfDocumentView_DocumentUnloaded;

            _grid.Children.Clear();
            _grid.Children.Add(pdfDocumentView);
            return pdfDocumentView;
        }

        private void _pdfDocumentView_DocumentLoaded(object sender, EventArgs args)
        {
            _dv = _pdvWrapper.innerPanel;
            _dvWrapper = _dv.GetWrapper();

            DocumentLoaded?.Invoke(this, args);
        }

        private void _pdfDocumentView_DocumentUnloaded(object sender, EventArgs e)
        {
            DocumentUnloaded?.Invoke(this, e);

            _dv = null;
            _dvWrapper = null;
        }

        public void Save(string fileName)
        {
            if (_pdv != null)
            {
                _originalFile.Unlock();

                _pdv.Save(fileName);

                _documentPath = fileName;
                _originalFile.Lock(_documentPath);
            }
        }

        private static Stream CreateNewCopiedStream(Stream input)
        {
            Stream newCopiedStream = new MemoryStream();
            byte[] buffer = new byte[input.Length];
            int count;
            while ((count = input.Read(buffer, 0, buffer.Length)) > 0)
                newCopiedStream.Write(buffer, 0, count);
            input.Position = 0L;
            return newCopiedStream;
        }

        #region Classes

        private class OriginalFile
        {
            private Stream _stream;

            public string FileName
            {
                get;
                private set;
            }

            public void Lock(string fileName)
            {
                try
                {
                    if (!string.IsNullOrEmpty(FileName))
                    {
                        if (fileName == FileName)
                            return;

                        Unlock();
                    }

                    _stream = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read);
                    FileName = fileName;
                }
                catch { }
            }

            public void Unlock()
            {
                if (_stream != null)
                {
                    _stream.Dispose();
                    _stream = null;
                }

                if (!string.IsNullOrEmpty(FileName))
                {
                    FileName = null;
                }
            }
        }

        #endregion

    }
}
